//
// Created by A on 2022/10/22.
//

#include "include/gmp.h"
#include "include/inner_gmp.h"
#include "include/time_sched.h"

#define WORKER_STACKS_SIZE 8192


gmp_env *env;


P *fetch_available_p(M *worker);

void *m_running_routine(void *arg);

void gmp_signal_handler();

int gmp_worker_routine(void *arg);

void register_task_timer();


void gmp_signal_handler() {
    int pid = getpid();
    m_running_arg *cur_arg = NULL;
    for (int i = 0; i < env->max_p * 2; i++) {
        if (pid == env->tid_map_list[i]) {
            cur_arg = env->m_arg_list + i;
            break;
        }
    }
    assert(cur_arg != NULL && cur_arg->self != NULL);


    P *related_p = cur_arg->self->related_p;
    if (related_p == NULL) {// 没有绑定到p，交给主线程来做
        return;
    }
    Loutine *cur = cur_arg->self->related_p->cur;
    if (cur == NULL) {//两种情况 1）没有绑定到g 2）g处理完了没有下一个
        return;
    }
    if (--cur->time_slot <= 0 && !(cur->state & (DONE_STATE | KILLED_STATE))) {
        Loutine *next = find_next_g_or_even_p(cur_arg->self, false);
        if (next) {
//        cur_arg->self->related_p->cur = next; 这句在fetch job做过了
            cur->time_slot = INIT_TIME_SLOT_SIZE; // 重设时间片
            register_task(cur);
            reschedule_gmp(cur, next);
        } else {
            related_p->cur = NULL;
            related_p->is_busy = false;
        }
    }
}


P *fetch_available_p(M *worker) {
    for (int i = 0; i < env->max_p; i++) {
        P *cur = env->plist + i;
        if (cur->is_busy) continue;
        M *prev_m = atomic_load(&cur->attached_m);
        if (prev_m == worker) {
            return cur;
        } else if (prev_m == NULL) {
            if (atomic_compare_exchange_strong(&cur->attached_m, &prev_m, worker)) {
                worker->related_p = cur;
                cur->is_busy = true;
                return cur;
            }
        }
    }
    return NULL;
}

void *m_running_routine(void *arg) {
    m_running_arg *running_arg = (m_running_arg *) arg;
    M *self = running_arg->self;
    Loutine *task = NULL;
    task = find_next_g_or_even_p(self, true);
    switch_to_loutine(task);
    assert(0);
}

int gmp_worker_routine(void *arg) {
    pthread_mutex_lock(&env->global_queue_mutex);
    for (int i = 0; i < env->max_p; i++) {
        if (env->tid_map_list[i] == 0) {
            env->tid_map_list[i] = getpid();
            break;
        }
    }
    pthread_mutex_unlock(&env->global_queue_mutex);
//    register_task_timer();
    m_running_routine(arg);
}

void register_task_timer() {
    srand((unsigned) time(NULL));
    int start_usec = rand() % MIN_SCHED_INTERVAL_USEC;

    struct itimerval value;
    value.it_value.tv_sec = 66666666;
    value.it_value.tv_usec = start_usec; // 100ms
    value.it_interval.tv_sec = 6666666;
    value.it_interval.tv_usec = USEC_PER_TICK; // 20ms per tick
    if (setitimer(ITIMER_REAL, &value, NULL) < 0) {
        perror("set timer error");
    }
    signal(SIGALRM, gmp_signal_handler);
}

Loutine *find_next_g_or_even_p(M *worker, bool is_spin) {

    for (;;) {
        if (!worker->related_p) {
            // fetch a new p to execute
            P *fetched_p = fetch_available_p(worker);
            while (!fetched_p) {
                // 如果没有取到，就block 500ms再次尝试
                // 这里可以进一步优化，让多余的m shutdown掉
                printf("found nothing in %d, sleeping\n", getpid());
                m_wait_ms(worker, 500);
                fetched_p = fetch_available_p(worker);
            }

        }
        worker->related_p->attached_m=worker;
        // 下面的逻辑需要改?

        // load job from related p
        Loutine *job = p_poll_job(worker->related_p, is_spin);

        if (job) {
            // run the job
            return job;
        }// else keep finding the workload

        if (!is_spin) {
            return NULL;
        }
    }
}


void init_gmp_env() {
    env = malloc(sizeof(gmp_env));
    assert(env);
    env->max_p = get_max_p();
    pthread_mutexattr_init(&env->shared_mutex_arg);
    pthread_mutexattr_setpshared(&env->shared_mutex_arg, PTHREAD_PROCESS_SHARED);
    pthread_mutex_init(&env->global_queue_mutex, &env->shared_mutex_arg);
    init_deque(&env->global_queue);
    env->plist = calloc(env->max_p, sizeof(P));
    env->mlist = calloc(env->max_p, sizeof(M));
    env->m_arg_list = calloc(env->max_p, sizeof(m_running_arg));
//    env->tid_map_list = calloc(env->max_p * 2, sizeof(int));
    memset(env->tid_map_list, 0, env->max_p * sizeof(int));

    // 先全部初始化，以免先创建的task访问到未初始化的内容
    for (int i = 0; i < env->max_p; i++) {
        M *m = env->mlist + i;
        init_m(m);
        P *related_p = env->plist + i;
        init_p(related_p);
        m->related_p = related_p;
        m_running_arg *arg = env->m_arg_list + i;
        arg->self = m;
        arg->stack = malloc(WORKER_STACKS_SIZE);
    }

    for (int i = 0; i < env->max_p; i++) {
        m_running_arg *arg = env->m_arg_list + i;
        clone(gmp_worker_routine,
              (char *) arg->stack + WORKER_STACKS_SIZE,
              CLONE_VM,
              arg);
    }


}

void init_m(M *m) {
    m->related_p = NULL;
    pthread_mutex_init(&m->mutex, NULL);
    pthread_cond_init(&m->cond, NULL);
}

void init_p(P *p) {
    p->is_busy = false;
    p->attached_m = NULL;
    p->pq = create_pqueue();
    p->cur = NULL;
}


int register_task(Loutine *loutine) {
    static int id_counter = 1;
    pthread_mutex_lock(&env->global_queue_mutex);
    printf("registered!!!!!!!!!!!!!!!!!!!!!!!!!!\n");
    add_last(&env->global_queue, loutine);
    pthread_mutex_unlock(&env->global_queue_mutex);
    return id_counter++;
}

int get_max_p() {
    if (env->max_p == 0) {
        env->max_p = sysconf(_SC_NPROCESSORS_ONLN) / 2;
    }
    return env->max_p;
}


void set_max_p(int max_p) {
    env->max_p = max_p;
}

Loutine *p_poll_job(P *p, bool is_spin) {
    // pq empty, fetch job from global queue
    BEGIN:
    if (p->pq->size == 0) {
        pthread_mutex_lock(&env->global_queue_mutex);
        printf("%d lock when poll in mode %d\n", getpid(), is_spin);
        if (env->global_queue.size == 0) {
            // steal from other p
            printf("%d throw after nothing\n", getpid());
            pthread_mutex_unlock(&env->global_queue_mutex);
            int max_p_pos = -1;
            size_t max_p_remain_task = 0;
            for (int i = 0; i < get_max_p(); i++) {
                P *iter = env->plist + i;
                if (iter == p || iter->pq->size == 0) continue;
                if (iter->pq->size > max_p_remain_task) {
                    max_p_remain_task = iter->pq->size;
                    max_p_pos = i;
                }
            }
            if (max_p_remain_task != 0) {
                printf("steal from p %d(with %d jobs at hand)\n",
                       max_p_pos, (env->plist + max_p_pos)->pq->size);
                pqueue_print((env->plist + max_p_pos)->pq);
                pq_steal_item((env->plist + max_p_pos)->pq, p->pq, STEAL_RATIO);
            }
        } else {
            // steal from global queue
            int steal_num = max((int) (STEAL_RATIO * env->global_queue.capacity), MIN_STEAL_NUM);
            printf("%d try steal %d job from global\n", getpid(), steal_num);
            fflush(stdout);

            for (int i = 0; i < steal_num; i++) {
                void *val = poll_first(&env->global_queue);
                if (val) {
                    pqueue_add(p->pq, 0, val);
                } else {
                    break;// nothing left
                }
            }
            printf("%d`s job num is %d\n", getpid(), p->pq->tmp_queue.size);
            printf("%d throw after poll\n", getpid());
            pthread_mutex_unlock(&env->global_queue_mutex);
        }
    }


    Loutine *loutine = pqueue_poll(p->pq);
    if (loutine == NULL) {
        if (is_spin) {
            usleep(2000 * 1000);
            goto BEGIN;
        } else {
            return NULL;
        }
    }
    if (loutine->state == KILLED_STATE || loutine->state == DONE_STATE) {
        if (loutine->state == KILLED_STATE) {
            free_loutine(loutine);
        }
        if (is_spin) {
            usleep(2000 * 1000);
            goto BEGIN;
        } else {
            return NULL;
        }
    }
    p->is_busy = true;
    p->cur = loutine;
    return loutine;
}


void init_m_arg(m_running_arg *arg) {

}

M *find_cur_m() {
    int pid = getpid();
    m_running_arg *cur_arg = NULL;
    for (int i = 0; i < env->max_p; i++) {
        if (pid == env->tid_map_list[i]) {
            cur_arg = env->m_arg_list + i;
            break;
        }
    }
    if (cur_arg == NULL) {
        return NULL;
    }
    return cur_arg->self;
}

void m_wait_ms(M *worker, time_t ms) {
    struct timespec wait_time;
    wait_time = diff_from_now(ms);
    pthread_cond_timedwait(&worker->cond, &worker->mutex, &wait_time);
}


